package LRES;

import java.util.List;

/**
 * 单结点输出BP神经网络，计算担保风险系数
 * keng die
 * 搞的头都大了……终于明白了=。=
 * 可能是这样的：先把所有样本训练一遍，然后再计算训练一遍后的样本误差(也就是说对于单个样本，不需要进行总体误差计算)。
 * 如果误差不在范围内，训练次数+1，然后如此反复，直到误差在范围内或者超过训练次数。
 */

public class OneOut {
	//	private Coefficient cf;//参数
	//	private List<Double[]> sample=cf.getSample();//样本数据
	//	private int inputNum = cf.getInputNum(); // 输入层神经元数目
	//	private int hideNum = cf.getHideNum(); // 隐层神经元数目，前馈网络隐藏层
	//	private int outputNum = cf.getOutputNum(); // 输出层神经元数模，各个命令的开关阀
	//	private double[][] hideWeight = cf.getHideWeight(); // 输入层至隐层权值，输入层或者隐层的行列位置无所谓。
	//	private double[][] outWeight = cf.getOutWeight(); // 隐层至输出层权值，这个矩阵的行列顺序需要和上面的对应
	//	private double HideWeightTemp[][] = new double[hideNum][inputNum]; // 保存前一组输入层到隐层的权值
	//	private double OutWeightTemp[][] = new double[outputNum][hideNum]; // 保存前一组隐层到输出层的权值
	//	private double[] hideThreshold = cf.getHideThreshold(); // 隐层的阈值
	//	private double[] outputThreshold = cf.getOutputThreshold(); // 输出层的阈值
	//	private double[] hideIn = cf.getHideIn(); // 隐层的输入,各个隐藏层的神经单元的内积,Sigmod函数所处理的参数
	//	private double[] outputIn = cf.getOutputIn(); // 输出层的输入，各个输出层的神经单元的内积，Sigmod函数所处理的参数
	//	private double[] hide = cf.getHide(); // 隐层的输出，s型函数之后的输出值
	//	private double[] output = cf.getOutput(); // 输出层的输出，s型函数之后的输出值
	//	private double alpha = cf.getAlpha(); // 动量因子，改进型bp算法使用
	//	private double error = cf.getError(); // 第m个样本的整体误差
	//	private double a = cf.getA(); // 输出层至隐层学习效率，反向传播，误差分析时用到
	//	private double b = cf.getB(); // 隐层至输入层学习效率，反向传播，误差分析时用到
	//	private double[] outputErr = cf.getOutputErr(); // δk 隐藏层到输出层的误差，一般化误差
	//	private double[] hideErr = cf.getHideErr(); // δj 输入层到隐藏层的误差，一般化误差
	//	private double[][] oldHide = cf.getOldHide(); // 新旧权值更新量交替--改进型Bp算法
	//	private double[][] oldOutput = cf.getOldOutput();
	//	private double Exceptation; // 期望值
	//	private double[] traininput; // 训练输入数组
	//	private double[] input=cf.getInput();
	//	private double precision=cf.getPrecision();  //精度
	//	private int counter=cf.getCounter(); //当前训练次数
	//	private int times=cf.getTimes();  //训练次数边界


	/**
	 * 初始化权值、阈值 由于Sigmoid函数的特性，初始权值分布在-0.5~0.5
	 */
	private static void initial(Coefficient cf) {
		int inputNum = cf.getInputNum();
		int hideNum = cf.getHideNum(); 
		int outputNum = cf.getOutputNum();
		double[][] hideWeight = cf.getHideWeight();
		double[][] outWeight = cf.getOutWeight();
		double[] hideThreshold = cf.getHideThreshold();
		double[] outputThreshold = cf.getOutputThreshold();
		/* 初始化输入层到隐层的权值，随机模拟-0.5~0.5 */
		for (int i = 0; i < inputNum; i++) {
			for (int j = 0; j < hideNum; j++) {
				hideWeight[i][j] = Math.random() - 0.5;
			}
		}

		/* 初始化隐层到输出层的权值，随机模拟-0.5~0.5 */
		for (int i = 0; i < hideNum; i++) {
			for (int j = 0; j < outputNum; j++) {
				outWeight[i][j] = Math.random() - 0.5;
			}
		}

		/* 隐层阈值初始化 */
		for (int i = 0; i < hideNum; i++) {
			hideThreshold[i] = Math.random() - 0.5;
		}

		/* 输出层阈值初始化 */
		for (int i = 0; i < outputNum; i++) {
			outputThreshold[i] = Math.random() - 0.5;
		}
	}

	/**
	 * 求净输入，输出
	 * @param input 输入
	 */
	private static void computeInOut(Coefficient cf,double[] input) {
		int inputNum = cf.getInputNum();
		int hideNum = cf.getHideNum(); 
		int outputNum = cf.getOutputNum();
		double[][] hideWeight = cf.getHideWeight();
		double[][] outWeight = cf.getOutWeight();
		double[] hideThreshold = cf.getHideThreshold();
		double[] outputThreshold = cf.getOutputThreshold();
		double[] hideIn=cf.getHideIn();
		double[] hide=cf.getHide();
		double[] outputIn=cf.getOutputIn();
		double[] output=cf.getOutput();

		double sigma1, sigma2;

		/* 求隐层内积HideIn和输出Hide */
		for (int i = 0; i < hideNum; i++) {
			sigma1 = 0;

			for (int j = 0; j < inputNum; j++)
				sigma1 = sigma1 + hideWeight[j][i] * input[j]; // 对于第i个隐层单元，sigma1=第i层所有{输入值*对应的权值}的和

			hideIn[i] = sigma1 - hideThreshold[i]; // 权值*输入值 总和 再减去阈值（源程序为加上阈值，这里做了修改。经证实应该是减去的）
			hide[i] = 1 / (1 + Math.exp(-hideIn[i])); // 隐层内积的s型函数输出 Math.exp(double x)
		}

		/* 求输出层内积OutputIn和输出Output */
		for (int i = 0; i < outputNum; i++) {
			sigma2 = 0;
			for (int j = 0; j < hideNum; j++)
				sigma2 = sigma2 + outWeight[j][i] * hide[j];

			outputIn[i] = sigma2 - outputThreshold[i];
			output[i] = 1 / (1 + Math.exp(-outputIn[i])); // 输出层内积的s型函数输出Math.exp(double x)
		}
	}

	/**
	 * 误差分析
	 * 输出层与隐层权值变化量 δk
	 * 隐层与输入层权值变化量 δj
	 */
	private static double errOutHide(Coefficient cf,double error,double[] exception) {
		int outputNum=cf.getOutputNum();
		int hideNum=cf.getHideNum();
		double[][] outWeight=cf.getOutWeight();
		double[] hideErr=cf.getHideErr();
		double[] outputErr=cf.getOutputErr();
		double[] outputIn=cf.getOutputIn();
		double[] output=cf.getOutput();
		double[] hideIn=cf.getHideIn();
		double absErr = 0;
		double sqrErr = 0; 
		double sum;
		double[] sigmaO = new double[outputNum];
		double[] sigmaH = new double[hideNum];

		//计算输出层与隐层之间变化量
		for (int i = 0; i < outputNum; i++) {
			absErr = exception[i] - output[i]; // 误差信号 e(n)=d(n)-y(n)
			sqrErr = sqrErr + (absErr) * (absErr);
			sigmaO[i]=Math.exp(-outputIn[i]) / ((1+Math.exp(-outputIn[i])) * (1+Math.exp(-outputIn[i])));
			outputErr[i] = -absErr * sigmaO[i]; // 变化量：-(期望-实际输出)*f'(输出层的输入) -δk
		}
		error+=sqrErr; // 整体误差。

		//计算隐层和输入层之间变化量
		for (int i = 0; i < hideNum; i++) {
			sum = 0;
			for (int j = 0; j < outputNum; j++)
				sum = sum + outputErr[j] * outWeight[i][j]; //这个公式真麻烦
			sigmaH[i]=Math.exp(-hideIn[i]) / ((1+Math.exp(-hideIn[i])) * (1+Math.exp(-hideIn[i])));
			hideErr[i] = -sum * sigmaH[i];//-δj
		}

		return error;
	}

	//	/**
	//	 * 误差分析
	//	 * 隐藏层的误差 δj
	//	 */
	//	private static void errHideIn(Coefficient cf) {
	//		int hideNum=cf.getHideNum();
	//		int outputNum=cf.getOutputNum();
	//		double[] outputErr=cf.getOutputErr();
	//		double[] hideErr=cf.getHideErr();
	//		double[][] outWeight=cf.getOutWeight();
	//		double[] hide=cf.getHide();
	//		
	//		double sigma;
	//		for (int i = 0; i < hideNum; i++) {
	//			sigma = 0;
	//			for (int j = 0; j < outputNum; j++)
	//				sigma = sigma + outputErr[j] * outWeight[j][i]; //这个公式真麻烦
	//
	//			hideErr[i] = sigma * hide[i] * (1 - hide[i]);
	//		}
	//	}

	/**
	 * 总误差 各样本w误差之和 当需要批量训练的时候才用
	 */
	//	private static double errorSum() {
	//		double total_err = 0;
	//
	//		for (Double a:error)
	//			total_err += a;
	//
	//		return total_err;
	//	}

	/**
	 * 新旧权值更新量交替--改进型Bp算法
	 */
	private static void saveWV(Coefficient cf,double[] traininput) {
		int hideNum=cf.getHideNum();
		int inputNum=cf.getInputNum();
		int outputNum=cf.getOutputNum();
		double a=cf.getA();
		double b=cf.getB();
		double[][] oldHide=cf.getOldHide();
		double[] hideErr=cf.getHideErr();
		double[][] oldOutput=cf.getOldOutput();
		double[] outputErr=cf.getOutputErr();
		double[] hide=cf.getHide();

		// 输入层到隐层的权值更新
		for (int i = 0; i < inputNum; i++) {
			for (int j = 0; j < hideNum; j++)
				oldHide[i][j] = b * hideErr[j] * traininput[i];
		}
		// 隐层到输出层的权值更新
		for (int i = 0; i < hideNum; i++) {
			for (int j = 0; j < outputNum; j++)
				oldOutput[i][j] = a * outputErr[j] * hide[i];
		}
	}

	/**
	 * 更新权值，阈值
	 * 
	 */
	private static void updateWeightThreshold(Coefficient cf,double[] traininput) {
		int outputNum=cf.getOutputNum();
		int hideNum=cf.getHideNum();
		int inputNum = cf.getInputNum();
		double[] hide=cf.getHide();
		double[][] hideWeight = cf.getHideWeight();
		double[][] hideWeightTemp=cf.getHideWeightTemp();
		double[] hideThreshold = cf.getHideThreshold();
		double[] hideThresholdTemp=cf.getHideThresholdTemp();
		double b=cf.getB();
		double[][] oldHide=cf.getOldHide();
		double alpha=cf.getAlpha();
		double a=cf.getA();
		double[][] oldOutput=cf.getOldOutput();
		double[] outputThreshold=cf.getOutputThreshold();
		double[] outputThresholdTemp=cf.getOutputThresholdTemp();
		double[][] outWeight=cf.getOutWeight();
		double[][] outWeightTemp=cf.getOutWeightTemp();
		//复制隐层-输出层权值数组
		for(int i=0;i<outWeight.length;i++)
			System.arraycopy(outWeight[i], 0, outWeightTemp[i], 0, outWeight[i].length);
		//复制输出层阈值数组
		System.arraycopy(outputThreshold, 0, outputThresholdTemp, 0, outputNum);

		for (int i = 0; i < hideNum; i++){
			for (int j = 0; j < outputNum; j++){
				outWeight[i][j] = outWeight[i][j] - a
				* oldOutput[i][j] + alpha * (outWeight[i][j]-outWeightTemp[i][j]);// 参看基于人工神经网络的商业银行贷款风险预警研究和BP神经网络.ppt
				outputThreshold[j] = outputThreshold[j]-a * outputThreshold[j]*oldOutput[i][j]/hide[i] + alpha * (outputThreshold[j]-outputThresholdTemp[j]);
			}
		}

		//复制输入层-隐层权值数组
		for(int i=0;i<hideWeight.length;i++)
			System.arraycopy(hideWeight[i], 0, hideWeightTemp[i], 0, hideWeight[i].length);
		//复制输出层阈值数组
		System.arraycopy(hideThreshold, 0, hideThresholdTemp, 0, hideNum);

		for (int i = 0; i < inputNum; i++) {
			for (int j = 0; j < hideNum; j++){
				hideWeight[i][j] = hideWeight[i][j] - b
				* oldHide[i][j] + alpha * (hideWeight[i][j]-hideWeightTemp[i][j]);
				hideThreshold[j] = hideThreshold[j]*oldHide[i][j]/traininput[i] + alpha * (hideThreshold[j]-hideThresholdTemp[j]);
			}
		}
	}

	//	/**
	//	 * 更新权值，阈值 隐层更新
	//	 */
	//	private static void updateHideIn(Coefficient cf,double[] traininput) {
	//		int inputNum = cf.getInputNum();
	//		int hideNum = cf.getHideNum(); 
	//		double[][] hideWeight = cf.getHideWeight();
	//		double[] hideThreshold = cf.getHideThreshold();
	//		double[] hideErr=cf.getHideErr();
	//		double b=cf.getB();
	//		double alpha=cf.getAlpha();
	//		double[][] oldHide=cf.getOldHide();
	//		for (int i = 0; i < hideNum; i++) {
	//			for (int j = 0; j < inputNum; j++)
	//				// Weight_In_Hide[j][i]=Weight_In_Hide[j][i]+b*e_err[j]*input[i];
	//				// Weight_In_Hide[j][i]=Weight_In_Hide[j][i]+(1.0-alpha)*b*e_err[j]*input[i]+alpha*b*e_err[j]*input[i];
	//				hideWeight[i][j] = hideWeight[i][j] + (1 - alpha) * b
	//				* hideErr[i] * traininput[j] + alpha * oldHide[i][j];
	//			hideThreshold[i] = hideThreshold[i] + b * hideErr[i];
	//		}
	//	}


	/**
	 * 训练BP神经网络
	 */
	public static void trainBP(Coefficient cf){
		List<double[]> sample=cf.getSample();//样本数据
		double[] exception = new double[1]; // 期望值
		double[] traininput=new double[sample.get(0).length-1]; // 训练输入数组
		double precision=cf.getPrecision();  //精度
		int counter=cf.getCounter(); //当前训练次数
		int times=cf.getTimes();  //训练次数边界
		double error=cf.getError();
		initial(cf);//初始化权值阈值
		while(counter<times){
			error=0;
			for(double[] s:sample){
				System.arraycopy(s, 0, traininput, 0, s.length-1);
				exception[0]=s[s.length-1];
				computeInOut(cf,traininput);
				System.out.println("误差："+((cf.getOutput()[0]-exception[0])/exception[0])*100);
				error=errOutHide(cf,error,exception);
				saveWV(cf,traininput);
				updateWeightThreshold(cf, traininput);
			}
			if(error<=precision){
				cf.setError(error);
				break;
			}
			else
				counter++;
		}
		cf.setError(error);
		cf.setCounter(counter);
	}

	/**
	 * 运行神经网络
	 */
	public static void runBP(Coefficient cf){
		double[] input=cf.getInput();
		computeInOut(cf,input);
	}
}